#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QRegularExpression>
#include <QDateTime>
#include <QMessageBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QValueAxis>
#include <csignal>  // 添加这个头文件
#include <QNetworkInterface>  // 网络接口相关
#include <QHostAddress>       // IP地址处理
#include <QDebug>
#include <chrono>
#include <unistd.h> // 在 Linux/Unix 系统


/*void MainWindow::handleFrameDone(const QString &response)
{
    //hwStatus.cameraState = MainWindow::Idle;
    QStringList parts = response.split(':');
    QString tag;

    foreach (const QString &part, parts) {
        if (part.startsWith("done=")) {
            tag = part.mid(5);
            break;
        }
    }

    if (tag.isEmpty()) {
        LOG_CAMERA("Invalid task ID");
        return;
    }

    // 记录收到的tag
    m_receivedTags.insert(tag);

    // 检查是否收到所有4个tag（00,01,02,03）
    if (m_receivedTags.size() == 4) {
        // 准备FITS头信息
        std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
        //if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
            double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
            //keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
            //keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
            //keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
           // keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());

            //LOG_CAMERA(QString("ADD TO FITS HEADER: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                     //  .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       //.arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       //.arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                     //  .arg(expTime));
      //  }

        // 使用QtConcurrent并行处理4个bin文件
        QList<QFuture<bool>> futures;
        QStringList fitsFilePaths;

        for (const QString &receivedTag : m_receivedTags) {
            QString binFile = QString("%1/%2.bin").arg(binPath).arg(receivedTag);
            QString fitsFile = fitsSavePath + "/" + currentBaseFilename + "_" + receivedTag + ".fits";
            fitsFilePaths.append(fitsFile);

            futures.append(QtConcurrent::run([this, binFile, receivedTag, keywords]() {
                return convertBinToFits(binFile, fitsSavePath,
                                        currentBaseFilename + "_" + receivedTag,
                                        keywords);
            }));
        }

        // 等待所有转换完成
        for (auto &future : futures) {
            future.waitForFinished();
        }

        // 检查所有FITS文件是否生成成功
        bool allFilesExist = true;
        for (const QString &fitsFile : fitsFilePaths) {
            if (!QFile::exists(fitsFile)) {
                allFilesExist = false;
                LOG_CAMERA(QString("The FITS file has not been generated: %1").arg(fitsFile));
                break;
            }
        }

        // 如果所有FITS文件都存在，则合并它们
        if (allFilesExist) {
            QString mergedFitsPath = fitsSavePath + "/" + currentBaseFilename;
            if(!m_currentExpInfo.isManual && m_pendingResponse.isValid()){
                auto& pending = m_pendingResponse;
                QString doneResponse = QString(
                                           "<ID=302><CamID=%1><TIME=%2><SHUTSTA=closed>"
                                           "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
                                           "<SENDTIME=%5><FITS_Name=%6>;")
                                           .arg(pending.camId)
                                           .arg(pending.expTime, 0, 'f', 2)
                                           .arg(pending.expStartTimeStr)
                                           .arg(hwStatus.currentTemp, 0, 'f', 2)
                                           .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                                           .arg(pending.imageName);

                sendResponse(doneResponse, pending.client);
                LOG_CAMERA(QString("Remote Exp completed. [Cam:%1][Tag:%2]").arg(pending.camId).arg(tag));
            }
            if (mergeFitsFiles(fitsFilePaths, mergedFitsPath)) {
                LOG_CAMERA(QString("mergeFitsFiles successfully: %1").arg(mergedFitsPath));
                recordShootingHistory(mergedFitsPath, fitsSavePath); // 记录历史

                // 可选：删除原始单个FITS文件
                for (const QString &fitsFile : fitsFilePaths) {
                    QFile::remove(fitsFile);
                }
            } else {
                LOG_CAMERA("mergeFitsFiles failed");
            }
        }
        QString mergedFitsPath1 = fitsSavePath + "/" + currentBaseFilename;
        QMetaObject::invokeMethod(this, [this, mergedFitsPath1]() {
            if (ui->checkboxShowFits->isChecked()) {
                launchDS9(mergedFitsPath1);
            }
        }, Qt::QueuedConnection);

        m_currentExpInfo = ExposureInfo();

        cameraState.status = 0;
        // 清空已接收的tag
        m_receivedTags.clear();
        PendingResponse pendingResponse;
        m_pendingResponse = pendingResponse;

        // 如果是序列拍摄模式，则继续下一个任务
        if (isSequenceRunning) {
            QTimer::singleShot(1000, this, &MainWindow::processSequenceNextFrame);
        }
        // 如果是循环模式且未完成，启动下一次拍摄
        if (ui->loopCheckBox->isChecked()){
            static int currentLoop = 0;
            static int totalLoops = 1;

            if(currentLoop > 0 && currentLoop < totalLoops) {
                 QTimer::singleShot(1000, this, &MainWindow::on_btnStartExposure_clicked);
            } else {
                currentLoop = 0;
                ui->labelProgressStatus->setText("准备就绪");
            }
        }
    }
}*/

// handleFrameDone函数:支持python转换方式
/*void MainWindow::handleFrameDone(const QString &response)
{
    QStringList parts = response.split(':');
    QString tag;

    foreach (const QString &part, parts) {
        if (part.startsWith("done=")) {
            tag = part.mid(5);
            break;
        }
    }

    if (tag.isEmpty()) {
        LOG_CAMERA(" 无效的任务标识");
        return;
    }
    binPath = "/home/csst/PS_mosaic/data/bin_tmp";
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    //QString fitsFile = QString("%1/%2.fits").arg(fitsSavePath).arg(tag);

    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
    if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());

        LOG_CAMERA(QString("添加时间戳到FITS头: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                       .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(expTime));
    }

    // 使用Python脚本转换
    QProcess process;
    QString python = "/home/csst/anaconda3/bin/python3";
    QString script = "/home/csst/PS_mosaic/tmp/bin2fits_250522.py";
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath).arg(currentBaseFilename).arg(tag);

    process.start(python, {script, binFile, fitsSavePath, currentBaseFilename});

    if (!process.waitForFinished(50000)) {
        LOG_CAMERA("转换超时");
    } else if (process.exitCode() != 0) {
        LOG_CAMERA(process.readAllStandardError());
    } else {
        LOG_CAMERA("转换成功: " + fitsFile);
        QString fitsName = QString("%1_%2.fits").arg(currentBaseFilename).arg(tag);
        recordShootingHistory(fitsName, fitsSavePath); // 记录历史
        
        // added by XYH@20250804, 利用全局变量fitsDone[0,1,2,3]来判断是不是所有4个fits都转换成功

        // 如果转换成功，更新FITS头信息
        //if (m_currentExpInfo.isValid() && m_currentExpInfo.tag == tag) {
        if (m_currentExpInfo.isValid()) {
            QJsonObject headerObj;
            for (const auto& keyword : keywords) {
                headerObj.insert(QString::fromStdString(keyword.first),
                                 QString::fromStdString(keyword.second));
            }

            // 在后台线程中更新FITS头
            QtConcurrent::run([this, fitsFile, headerObj]() {
                updateFitsHeader(fitsFile, headerObj);
            });
        }

        // 如果是序列拍摄模式，则继续下一个任务
        if (isSequenceRunning && (QString("00") == tag)) {
            QTimer::singleShot(5000, this, &MainWindow::processSequenceNextFrame);
        }
    }
    resetFrameCounter();
}*/
//python
/*
void MainWindow::handleFrameDone(const QString &response)
{
    QStringList parts = response.split(':');
    QString tag;

    foreach (const QString &part, parts) {
        if (part.startsWith("done=")) {
            tag = part.mid(5);
            break;
        }
    }

    if (tag.isEmpty()) {
        LOG_CAMERA(" Invalid task ID");
        return;
    }

    // 记录收到的tag
    m_receivedTags.insert(tag);

    binPath = "/home/csst/PS_mosaic/data/bin_tmp";
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    //QString fitsFile = QString("%1/%2.fits").arg(fitsSavePath).arg(tag);

    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
    if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
        keywords.emplace_back("TEMP", QString::number(hwStatus.currentTemp).toStdString());
        keywords.emplace_back("VACCUM", QString::number(hwStatus.currentVacuum, 'e', 3).toStdString());
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
        keywords.emplace_back("SETEXPTIME", QString::number(m_currentExpInfo.setexptime, 'f', 3).toStdString());
        if (std::abs(m_currentExpInfo.setexptime - expTime) > 1e-4 ) {
            LOG_CAMERA("EXPTIME ERROR!!!!!!!!!!!!!!!!!!!!!!!1");
            on_btnAbort_clicked();
        }

        LOG_CAMERA(QString("ADD TO FITS HEADER: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                       .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(expTime));
    }

    // 使用Python脚本转换
    QProcess process;
    QString python = "/home/csst/anaconda3/bin/python3";
    QString script = "/home/csst/PS_mosaic/tmp/bin2fits_250522.py";
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath).arg(currentBaseFilename).arg(tag);

    process.start(python, {script, binFile, fitsSavePath, currentBaseFilename});

    if (!process.waitForFinished(50000)) {
        LOG_CAMERA("Conversion timed out");
    } else if (process.exitCode() != 0) {
        LOG_CAMERA(process.readAllStandardError());
    } else {
        LOG_CAMERA("Conversion successfully: " + fitsFile);
        QString fitsName = QString("%1_%2.fits").arg(currentBaseFilename).arg(tag);
        recordShootingHistory(fitsName, fitsSavePath); // 记录历史

        // 如果转换成功，更新FITS头信息
        //if (m_currentExpInfo.isValid() && m_currentExpInfo.tag == tag) {
        if (m_currentExpInfo.isValid()) {
            QJsonObject headerObj;
            for (const auto& keyword : keywords) {
                headerObj.insert(QString::fromStdString(keyword.first),
                                 QString::fromStdString(keyword.second));
            }


            // 在后台线程中更新FITS头
            QtConcurrent::run([this, fitsFile, headerObj]() {
                updateFitsHeader(fitsFile, headerObj);
            });
        }
       // if( !m_currentExpInfo.isManual && m_pendingResponse.isValid()){
//        if( m_pendingResponse.isValid() &&  !m_currentExpInfo.isManual && m_receivedTags.size() == 4){
        //if( m_pendingResponse.isValid() &&  !m_currentExpInfo.isManual && m_receivedTags.size() >=3 ){// 加了这行代码后，normal_ptc的序列似乎不再卡死了。。。后来又卡死了。。。
        if (!m_currentExpInfo.isManual) {
            if (m_receivedTags.size() == 4) {
                LOG_CAMERA("!!!!!  m_receivedTags.size() = 4 !!!!!!");
                if(m_pendingResponse.isValid()) {
                    LOG_CAMERA("m_pendingResponse.isValid()!!!!!!!! ");
                    QString doneResponse = QString(
                                               "<ID=302><CamID=%1><TIME=%2><SHUTSTA=closed>"
                                               "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
                                               "<SENDTIME=%5><FITS_Name=%6>;")
                                               .arg(m_pendingResponse.camId)
                                               .arg(m_pendingResponse.expTime, 0, 'f', 2)
                                               .arg(m_pendingResponse.expStartTimeStr)
                                               .arg(hwStatus.currentTemp, 0, 'f', 2)
                                               .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                                               .arg(m_pendingResponse.imageName);

                    sendResponse(doneResponse, m_pendingResponse.client);
                    LOG_CAMERA(QString("Remote Exp completed. [Cam:%1][Tag:%2]").arg(m_pendingResponse.camId).arg(tag));
                    // 清空已接收的tag
                    m_receivedTags.clear();

                    // 等待数据发送完成后优雅关闭
                    m_pendingResponse.client->disconnectFromHost();

                    // 让 Qt 自动在断开后释放资源
                    connect(m_pendingResponse.client, &QTcpSocket::disconnected, m_pendingResponse.client, &QObject::deleteLater);
                }
                else {
                    LOG_CAMERA("!!!!!m_pendingResponse ERROR!!!!!!!! ");
                }
            }
            else {
                LOG_CAMERA(QString("m_receivedTags.size() = %1").arg(m_receivedTags.size()));
            }
        }
        else {
            LOG_CAMERA("!!!!! isManual !!!!!!");
        }

        // 如果是序列拍摄模式，则继续下一个任务
        // if (isSequenceRunning && (QString("00") == tag)) {
        //     QTimer::singleShot(5000, this, &MainWindow::processSequenceNextFrame);
        // }
    }
    resetFrameCounter();
}*/
/*old shuchuan,you bin banben
void MainWindow::handleFrameDone(const QString &response, QPointer<QTcpSocket> client)
{
    // 1. 参数校验
    if (response.isEmpty()) {
        LOG_CAMERA("Error: Empty response received in handleFrameDone");
        if (client) {
            sendResponse("<ID=302><INFO=Error><MSG=Empty_response>", client);
        }
        return;
    }

    // 2. 解析响应获取tag
    QString tag;
    int donePos = response.indexOf("done=");
    if (donePos != -1) {
        tag = response.mid(donePos + 5, 2); // 提取00/01/02/03
    }

    if (tag.isEmpty() || !(tag == "00" || tag == "01" || tag == "02" || tag == "03")) {
        LOG_CAMERA(QString("Invalid tag format in response: %1").arg(response));
        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=Invalid_tag_format_%1>").arg(response.left(20)), client);
        }
        return;
    }

    // 3. 构建文件路径
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath)
                           .arg(currentBaseFilename).arg(tag);

    // 4. 检查bin文件是否存在
    if (!QFile::exists(binFile)) {
        LOG_CAMERA(QString("Bin file not found: %1").arg(binFile));
        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=Bin_file_not_found_%1>").arg(tag), client);
        }
        return;
    }

    // 5. 准备FITS头信息
    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
    if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
        keywords.emplace_back("TEMP", QString::number(hwStatus.currentTemp).toStdString());
        keywords.emplace_back("VACCUM", QString::number(hwStatus.currentVacuum, 'e', 3).toStdString());
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
        keywords.emplace_back("SETEXPTIME", QString::number(m_currentExpInfo.setexptime, 'f', 3).toStdString());
        if (std::abs(m_currentExpInfo.setexptime - expTime) > 1e-4 ) {
            LOG_CAMERA("EXPTIME ERROR!!!!!!!!!!!!!!!!!!!!!!!1");
            on_btnAbort_clicked();
        }

        LOG_CAMERA(QString("ADD TO FITS HEADER: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                       .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(expTime));
    }

    // 6. 使用Python脚本转换
    QProcess process;
    QString python = "/home/csst/anaconda3/bin/python3";
    QString script = "/home/csst/PS_mosaic/tmp/bin2fits_250522.py";

    LOG_CAMERA(QString("Starting conversion for tag %1: %2").arg(tag).arg(binFile));

    process.start(python, {script, binFile, fitsSavePath, currentBaseFilename});

    // 设置超时（30秒）
    if (!process.waitForFinished(30000)) {
        QString errorMsg = QString("Conversion timeout for tag %1").arg(tag);
        LOG_CAMERA(errorMsg);

        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=%1>").arg(errorMsg), client);
        }

        // 尝试终止卡住的进程
        process.kill();
        return;
    }

    // 检查转换结果
    if (process.exitCode() != 0) {
        QString errorOutput = process.readAllStandardError();
        QString errorMsg = QString("Conversion failed for tag %1: %2").arg(tag).arg(errorOutput);
        LOG_CAMERA(errorMsg);

        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=%1>").arg(errorMsg), client);
        }
        return;
    }

    // 7. 验证FITS文件
    if (!QFile::exists(fitsFile)) {
        QString errorMsg = QString("FITS file not created: %1").arg(fitsFile);
        LOG_CAMERA(errorMsg);

        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=%1>").arg(errorMsg), client);
        }
        return;
    }

    // 8. 更新FITS头信息（在后台线程）
    QtConcurrent::run([this, fitsFile, keywords]() {
        QJsonObject headerObj;
        for (const auto& keyword : keywords) {
            headerObj.insert(QString::fromStdString(keyword.first),
                             QString::fromStdString(keyword.second));
        }

        if (!updateFitsHeader(fitsFile, headerObj)) {
            LOG_CAMERA(QString("Failed to update FITS header for %1").arg(fitsFile));
        }
    });

    // 9. 记录成功日志
    LOG_CAMERA(QString("Successfully processed tag %1 -> %2").arg(tag).arg(fitsFile));

    // 注意：这里不发送完成响应，由监听线程在收到全部4个tag后统一发送
    // 仅当处理失败时才发送错误响应

    // 10. 如果是最后一个tag，触发显示（如果启用）
    static QSet<QString> receivedTags;
    receivedTags.insert(tag);

    if (receivedTags.size() == 4) {
        receivedTags.clear();

        // 触发DS9显示（如果启用）
        if (ui->checkboxShowFits->isChecked()) {
            QString mergedFitsPath = fitsSavePath + "/" + currentBaseFilename + ".fits";
            QMetaObject::invokeMethod(this, [this, mergedFitsPath]() {
                launchDS9(mergedFitsPath);
            }, Qt::QueuedConnection);
        }
    }
}*/

//new shuchuan,no bin,you 302 jianting xiancheng
/*
void MainWindow::handleFrameDone(const QString &response, QPointer<QTcpSocket> client)
{
    // 1. 参数校验
    if (response.isEmpty()) {
        LOG_CAMERA("Error: Empty response received in handleFrameDone");
        if (client) {
            sendResponse("<ID=302><INFO=Error><MSG=Empty_response>", client);
        }
        return;
    }

    // 2. 解析响应获取tag
    QString tag;
    int donePos = response.indexOf("done=");
    if (donePos != -1) {
        tag = response.mid(donePos + 5, 2); // 提取00/01/02/03
    }

    if (tag.isEmpty() || !(tag == "00" || tag == "01" || tag == "02" || tag == "03")) {
        LOG_CAMERA(QString("Invalid tag format in response: %1").arg(response));
        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=Invalid_tag_format_%1>").arg(response.left(20)), client);
        }
        return;
    }



    // 3. 构建文件路径
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    if(fitsSavePath.startsWith("/mnt/")) {
        fitsSavePath = "/home/csst/PS_mosaic/" + fitsSavePath.mid(5);
    }
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath)
                           .arg(currentBaseFilename).arg(tag);


    //4. 检查fits文件是否存在
    if (!QFile::exists(fitsFile)) {
        LOG_CAMERA(QString("fits file not found: %1").arg(fitsFile));
        if (client) {
            sendResponse(QString("<ID=302><INFO=Error><MSG=Bin_file_not_found_%1>").arg(tag), client);
        }
        return;
    }
    // 5. 准备FITS头信息
    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
    if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
        keywords.emplace_back("TEMP", QString::number(hwStatus.currentTemp).toStdString());
        keywords.emplace_back("VACCUM", QString::number(hwStatus.currentVacuum, 'e', 3).toStdString());
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
        keywords.emplace_back("SETEXPTIME", QString::number(m_currentExpInfo.setexptime, 'f', 3).toStdString());
        if (std::abs(m_currentExpInfo.setexptime - expTime) > 1e-4 ) {
            LOG_CAMERA("EXPTIME ERROR!!!!!!!!!!!!!!!!!!!!!!!1");
            on_btnAbort_clicked();
        }

        LOG_CAMERA(QString("ADD TO FITS HEADER: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                       .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(expTime));
    }

    // 8. 更新FITS头信息（在后台线程）
    QtConcurrent::run([this, fitsFile, keywords]() {
        QJsonObject headerObj;
        for (const auto& keyword : keywords) {
            headerObj.insert(QString::fromStdString(keyword.first),
                             QString::fromStdString(keyword.second));
        }

        if (!updateFitsHeader(fitsFile, headerObj)) {
            LOG_CAMERA(QString("Failed to update FITS header for %1").arg(fitsFile));
        }
    });

    // 9. 记录成功日志
    LOG_CAMERA(QString("Successfully processed tag %1 -> %2").arg(tag).arg(fitsFile));

    // 注意：这里不发送完成响应，由监听线程在收到全部4个tag后统一发送
    // 仅当处理失败时才发送错误响应

    // 10. 如果是最后一个tag，触发显示（如果启用）
    static QSet<QString> receivedTags;
    receivedTags.insert(tag);

    if (receivedTags.size() == 4) {
        receivedTags.clear();

        // 触发DS9显示（如果启用）
        if (ui->checkboxShowFits->isChecked()) {
            QString mergedFitsPath = fitsSavePath + "/" + currentBaseFilename + ".fits";
            QMetaObject::invokeMethod(this, [this, mergedFitsPath]() {
                launchDS9(mergedFitsPath);
            }, Qt::QueuedConnection);
        }
    }
}*/
/*0816
void MainWindow::handleFrameDone(const QString &response)
{
    // 1. 参数校验
    if (response.isEmpty()) {
        LOG_CAMERA("Error: Empty response received in handleFrameDone");
        return;
    }

    // 2. 解析响应获取tag
    QString tag;
    int donePos = response.indexOf("done=");
    if (donePos != -1) {
        tag = response.mid(donePos + 5, 2); // 提取00/01/02/03
    }

    if (tag.isEmpty() || !(tag == "00" || tag == "01" || tag == "02" || tag == "03")) {
        LOG_CAMERA(QString("Invalid tag format in response: %1").arg(response));
        return;
    }
    // 记录收到的tag
    m_receivedTags.insert(tag);
    // 3. 构建文件路径
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    if(fitsSavePath.startsWith("/mnt/")) {
        fitsSavePath = "/home/csst/PS_mosaic/" + fitsSavePath.mid(5);
    }
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath)
                           .arg(currentBaseFilename).arg(tag);


    //4. 检查fits文件是否存在
    if (!QFile::exists(fitsFile)) {
        LOG_CAMERA(QString("fits file not found: %1").arg(fitsFile));
        return;
    }
    // 5. 准备FITS头信息
    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
    if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
        keywords.emplace_back("TEMP", QString::number(hwStatus.currentTemp).toStdString());
        keywords.emplace_back("VACCUM", QString::number(hwStatus.currentVacuum, 'e', 3).toStdString());
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
        keywords.emplace_back("SETEXPTIME", QString::number(m_currentExpInfo.setexptime, 'f', 3).toStdString());
        if (std::abs(m_currentExpInfo.setexptime - expTime) > 1e-4 ) {
            LOG_CAMERA("EXPTIME ERROR!!!!!!!!!!!!!!!!!!!!!!!1");
            on_btnAbort_clicked();
        }

        LOG_CAMERA(QString("ADD TO FITS HEADER: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                       .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(expTime));
    }

    // 8. 更新FITS头信息（在后台线程）
    QtConcurrent::run([this, fitsFile, keywords]() {
        QJsonObject headerObj;
        for (const auto& keyword : keywords) {
            headerObj.insert(QString::fromStdString(keyword.first),
                             QString::fromStdString(keyword.second));
        }

        if (!updateFitsHeader(fitsFile, headerObj)) {
            LOG_CAMERA(QString("Failed to update FITS header for %1").arg(fitsFile));
        }
    });


    // 9. 记录成功日志
    LOG_CAMERA(QString("Successfully processed tag %1 -> %2").arg(tag).arg(fitsFile));
    if (!m_currentExpInfo.isManual) {
        if (m_receivedTags.size() == 4) {
            LOG_CAMERA("!!!!!  m_receivedTags.size() = 4 !!!!!!");
            if(m_pendingResponse.isValid()) {

                LOG_CAMERA("m_pendingResponse.isValid()!!!!!!!! ");
                QString doneResponse = QString(
                                           "<ID=302><CamID=%1><TIME=%2><SHUTSTA=closed>"
                                           "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
                                           "<SENDTIME=%5><FITS_Name=%6>;")
                                           .arg(m_pendingResponse.camId)
                                           .arg(m_pendingResponse.expTime, 0, 'f', 2)
                                           .arg(m_pendingResponse.expStartTimeStr)
                                           .arg(hwStatus.currentTemp, 0, 'f', 2)
                                           .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                                           .arg(m_pendingResponse.imageName);

                sendResponse(doneResponse, m_pendingResponse.client);
                LOG_CAMERA(QString("Remote Exp completed. [Cam:%1][Tag:%2]").arg(m_pendingResponse.camId).arg(tag));
                // 清空已接收的tag
                m_receivedTags.clear();

                // 等待数据发送完成后优雅关闭
                m_pendingResponse.client->disconnectFromHost();

                // 让 Qt 自动在断开后释放资源
                connect(m_pendingResponse.client, &QTcpSocket::disconnected, m_pendingResponse.client, &QObject::deleteLater);
            }
            else {
                LOG_CAMERA("!!!!!m_pendingResponse ERROR!!!!!!!! ");
            }
        }
        else {
            LOG_CAMERA(QString("m_receivedTags.size() = %1").arg(m_receivedTags.size()));
        }
    }
    else {
        LOG_CAMERA("!!!!! isManual !!!!!!");
    }

    // 注意：这里不发送完成响应，由监听线程在收到全部4个tag后统一发送
    // 仅当处理失败时才发送错误响应

    // 10. 如果是最后一个tag，触发显示（如果启用）
    static QSet<QString> receivedTags;
    receivedTags.insert(tag);

    if (receivedTags.size() == 4) {
        receivedTags.clear();

        // 触发DS9显示（如果启用）
        if (ui->checkboxShowFits->isChecked()) {
            QString mergedFitsPath = fitsSavePath + "/" + currentBaseFilename + ".fits";
            QMetaObject::invokeMethod(this, [this, mergedFitsPath]() {
                launchDS9(mergedFitsPath);
            }, Qt::QueuedConnection);
        }
    }
}*/

void MainWindow::handleFrameDone(const QString &response)
{
    // 1. 参数校验
    if (response.isEmpty()) {
        LOG_CAMERA("Error: Empty response received in handleFrameDone");
        return;
    }

    // 2. 解析响应获取tag
    QString tag;
    int donePos = response.indexOf("done=");
    if (donePos != -1) {
        tag = response.mid(donePos + 5, 2); // 提取00/01/02/03
    }

    if (tag.isEmpty() || !(tag == "00" || tag == "01" || tag == "02" || tag == "03")) {
        LOG_CAMERA(QString("Invalid tag format in response: %1").arg(response));
        return;
    }
    // 记录收到的tag
    m_receivedTags.insert(tag);
    // 3. 构建文件路径
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    if(fitsSavePath.startsWith("data/")) {
        fitsSavePath = "/home/csst/PS_mosaic/" + fitsSavePath;
    }
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath)
                           .arg(currentBaseFilename).arg(tag);


    //4. 检查fits文件是否存在
    if (!QFile::exists(fitsFile)) {
        LOG_CAMERA(QString("fits file not found: %1").arg(fitsFile));
        return;
    }
    // 5. 准备FITS头信息
    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    // 如果当前曝光信息匹配，添加时间戳关键字
    if (m_currentExpInfo.isValid()) {
        // 计算曝光时间（秒）
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;

        // 添加时间戳关键字
        keywords.emplace_back("TEMP", QString::number(hwStatus.currentTemp).toStdString());
        keywords.emplace_back("VACCUM", QString::number(hwStatus.currentVacuum, 'e', 3).toStdString());
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
        keywords.emplace_back("SETTIME", QString::number(m_currentExpInfo.setexptime, 'f', 3).toStdString());
        if (std::abs(m_currentExpInfo.setexptime - expTime) > 1e-2 ) {
            LOG_CAMERA("EXPTIME ERROR!!!!!!!!!!!!!!!!!!!!!!!1");
            //on_btnAbort_clicked();
        }

        LOG_CAMERA(QString("ADD TO FITS HEADER: EXPSTART=%1, EXPEND=%2, READOUTS=%3, EXPTIME=%4")
                       .arg(m_currentExpInfo.expStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.expEnd.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(m_currentExpInfo.readoutStart.toString("yyyy-MM-dd hh:mm:ss.zzz"))
                       .arg(expTime));
    }

    // 8. 更新FITS头信息（在后台线程）
    QtConcurrent::run([this, fitsFile, keywords]() {
        QJsonObject headerObj;
        for (const auto& keyword : keywords) {
            headerObj.insert(QString::fromStdString(keyword.first),
                             QString::fromStdString(keyword.second));
        }

        if (!updateFitsHeader(fitsFile, headerObj)) {
            LOG_CAMERA(QString("Failed to update FITS header for %1").arg(fitsFile));
        }
    });
    // 9. 记录成功日志
    LOG_CAMERA(QString("Successfully processed tag %1 -> %2").arg(tag).arg(fitsFile));

    if (!m_currentExpInfo.isManual && m_receivedTags.size() == 4) {
        if(m_pendingResponse.isValid()) {
            sendCompletionResponse();
        }
    }
    // 10. 如果是最后一个tag，触发显示（如果启用）
    static QSet<QString> receivedTags;
    receivedTags.insert(tag);

    if (receivedTags.size() == 4) {
        receivedTags.clear();

        // 触发DS9显示（如果启用）
        /*
        if (ui->checkboxShowFits->isChecked()) {
            QString mergedFitsPath = fitsSavePath + "/" + currentBaseFilename + ".fits";
            QMetaObject::invokeMethod(this, [this, mergedFitsPath]() {
                launchDS9(mergedFitsPath);
            }, Qt::QueuedConnection);
        }*/
    }
}

// 新增辅助函数：发送完成响应
void MainWindow::sendCompletionResponse()
{
    if(!m_pendingResponse.isValid()) return;

    QString doneResponse = QString(
                               "<ID=302><CamID=%1><TIME=%2><SHUTSTA=closed>"
                               "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
                               "<SENDTIME=%5><FITS_Name=%6>;")
                               .arg(m_pendingResponse.camId)
                               .arg(m_pendingResponse.expTime, 0, 'f', 2)
                               .arg(m_pendingResponse.expStartTimeStr)
                               .arg(hwStatus.currentTemp, 0, 'f', 2)
                               .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                               .arg(m_pendingResponse.imageName);
    sleep(1);

    sendResponseThreadSafe(doneResponse, m_pendingResponse.client);
    LOG_CAMERA("Final completion response sent");

    // 清理资源
    m_receivedTags.clear();
    m_pendingResponse.client->disconnectFromHost();
    connect(m_pendingResponse.client, &QTcpSocket::disconnected,
            m_pendingResponse.client, &QObject::deleteLater);
}

//0818
/*
void MainWindow::handleFrameDone(const QString &response)
{
    QStringList parts = response.split(':');
    QString tag;

    LOG_CAMERA("enter handleframedone");
    foreach (const QString &part, parts) {
        if (part.startsWith("done=")) {
            tag = part.mid(5);

            break;
        }
    }

    if (tag.isEmpty()) {
        LOG_CAMERA("Invalid task ID");
        return;
    }

    // 记录收到的tag
    m_receivedTags.insert(tag);

    // 如果该tag已经在转换中或已完成，则忽略
    if (m_bin2fitsProcesses.contains(tag) || m_convertedTags.contains(tag)) {
        LOG_CAMERA(QString("Tag %1 is already being processed").arg(tag));
        return;
    }
    if(fitsSavePath.startsWith("/mnt/")) {
        fitsSavePath = "/home/csst/PS_mosaic/" + fitsSavePath;
    }

    // 准备转换参数
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath)
                           .arg(currentBaseFilename)
                           .arg(tag);
    fitsFilePaths.append(fitsFile);

    // 创建并启动非阻塞转换进程
    QProcess* process = new QProcess(this);
    process->setProperty("tag", tag); // 保存tag信息
    process->setProperty("fitsFile", fitsFile);
    LOG_CAMERA(fitsFile);

    connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
            this, &MainWindow::handleConversionFinished);
    //connect(process, &QProcess::errorOccurred,
           // this, &MainWindow::handleConversionError);

//    QString python = "/home/csst/anaconda3/bin/python3";
    QString python = "/home/meph/anaconda3/bin/python3";
//    QString script = "/home/csst/PS_mosaic/tmp/bin2fits_250522.py";
    QString script = "/usr/local/bin/bin2fits_250522.py";

    LOG_CAMERA(QString("Starting parallel conversion for tag %1").arg(tag));
    process->start(python, {script, binFile, fitsSavePath, currentBaseFilename});

    m_bin2fitsProcesses[tag] = process;
}*/

// 处理转换完成
void MainWindow::handleConversionFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    QProcess* process = qobject_cast<QProcess*>(sender());
    if (!process) return;

    QString tag = process->property("tag").toString();
    QString fitsFile = process->property("fitsFile").toString();
    //LOG_CAMERA(fitsFile);

    if (exitStatus == QProcess::NormalExit && exitCode == 0) {
        LOG_CAMERA(QString("Conversion succeeded for tag %1").arg(tag));
        {
            QMutexLocker locker(&m_tagsMutex);  // 自动加锁
            m_convertedTags.insert(tag);        // 受保护的操作
        }

        // 更新FITS头信息
        LOG_CAMERA(fitsFile);
        updateFitsHeaderAsync(fitsFile);

        // 记录历史
        //recordShootingHistory(QFileInfo(fitsFile).fileName(), fitsSavePath);
    } else {
        LOG_CAMERA(QString("Conversion failed for tag %1, code: %2")
                       .arg(tag).arg(exitCode));
        LOG_CAMERA(fitsFile);
        updateFitsHeaderAsync(fitsFile);
    }

    // 清理进程
    m_bin2fitsProcesses.remove(tag);
    process->deleteLater();

    // 检查是否全部完成
    checkAllConversionsDone();
}

// 处理转换错误
void MainWindow::handleConversionError(QProcess::ProcessError error)
{
    QProcess* process = qobject_cast<QProcess*>(sender());
    if (!process) return;

    QString tag = process->property("tag").toString();
    LOG_CAMERA(QString("Conversion error for tag %1: %2")
                   .arg(tag).arg(process->errorString()));

    m_bin2fitsProcesses.remove(tag);
    process->deleteLater();
}

// 异步更新FITS头
void MainWindow::updateFitsHeaderAsync(const QString& fitsFile)
{
    LOG_CAMERA("enter updateFitsHeaderAsync");
    std::vector<std::pair<std::string, std::string>> keywords = collectManualHeaderInfo();

    if (m_currentExpInfo.isValid()) {
        LOG_CAMERA("m_currentExpInfo.isValid");
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0 * 0.1;

        keywords.emplace_back("TEMP", QString::number(hwStatus.currentTemp).toStdString());
        keywords.emplace_back("VACCUM", QString::number(hwStatus.currentVacuum, 'e', 3).toStdString());
        keywords.emplace_back("EXPSTART", m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
    }


    QtConcurrent::run([this, fitsFile, keywords]() {
        QJsonObject headerObj;
        for (const auto& keyword : keywords) {
            headerObj.insert(QString::fromStdString(keyword.first),
                             QString::fromStdString(keyword.second));
        }
        updateFitsHeader(fitsFile, headerObj);
    });
}
/*bool mergeFitsFiles(const QStringList& inputFiles, const QString& outputFile, const QString& filename,
                    const QString& pathname, const QString& date_,
                    const double& TEMP_1,
                    const double& TEMP_2,
                    const double& TEMP_3,
                    const double& TEMP_4,
                    const double& TEMP,
                    const QString& CCD_SN,
                    const QString& imagetype,
                    const QString& imgrect,
                    const QString& readmode,
                    const QString& channel,
                    const double& EXPTIME, const double& EXPREQ, const QString& EXPSTA, const QString& EXPEND);*/
// 检查所有转换是否完成
void MainWindow::checkAllConversionsDone()
{
    //if (m_receivedTags.size() == 4 && m_convertedTags.size() == 4) {
    if (m_receivedTags.size() == 4 ) {
        LOG_CAMERA("All 4 BIN files converted successfully");
        QString mergedFitsPath = fitsSavePath + "/" + currentBaseFilename +".fits";

        QString mergefitsfile = currentBaseFilename +".fits";
        //if (mergeFitsFiles(fitsFilePaths, mergedFitsPath, )) {
        QString imagetype = ui->comBoxframetype->currentData().toString();
        QString date_ = QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss.zzz");
        QString readmode = m_currentExpInfo.readoutRate;
        QString channel = "blue";
        double temp_1 = hwStatus.tempData.ccd1;
        double temp_2 = hwStatus.tempData.ccd2;
        double temp_3 = hwStatus.tempData.ccd3;
        double temp_4 = hwStatus.tempData.ccd4;
        double temp = hwStatus.currentTemp;
        QString imgrect = "";
        QString CCD_SN = "";
        double expTime = m_currentExpInfo.expStart.msecsTo(m_currentExpInfo.expEnd) / 1000.0;
        double expreq = ui->doubleSpinBoxExposureTime->value();
        QString expsta = m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz");
        QString expend = m_currentExpInfo.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz");
        QString utcexpsta = beijingToUTC(expsta);
        QString utcexpend= beijingToUTC(expend);


        if (mergeFitsFiles(fitsFilePaths, mergedFitsPath, mergefitsfile, fitsSavePath, date_, temp_1, temp_2, temp_3, temp_4, temp,
                           CCD_SN,imagetype, imgrect, readmode, channel, expTime, expreq, utcexpsta, utcexpend)) {
            LOG_CAMERA(QString("mergeFitsFiles successfully: %1").arg(mergedFitsPath));

            recordShootingHistory(mergedFitsPath, fitsSavePath); // 记录历史
            QString subFolderPath = fitsSavePath + "/sub";
            QDir().mkpath(subFolderPath);  // 创建目录
            /*
            for (const QString &fitsFile : fitsFilePaths) {
                if (!moveFitsToSubfolder(fitsFile, subFolderPath)) {
                    qCritical() << "Failed to process file:" << fitsFile;
                }
            }*/
            fitsFilePaths.clear();
            QMetaObject::invokeMethod(this, [this, mergedFitsPath]() {
                if (ui->checkboxShowFits->isChecked()) {
                    launchDS9(mergedFitsPath);
                }
            }, Qt::QueuedConnection);
        } else {
            LOG_CAMERA("mergeFitsFiles failed");
            fitsFilePaths.clear();
        }

        if( m_pendingResponse.isValid() ){
            sendCompletion302Response();
        }
        /*if (!m_currentExpInfo.isManual && m_pendingResponse.isValid()) {
            queryCameraStatus([this]() {
                if (cameraState.status == 0) {
                    sendCompletion302Response();
                } else {
                    QTimer::singleShot(500, this, [this]() {
                        queryCameraStatus([this]() { // 保持递归调用
                            if (cameraState.status == 0) {
                                sendCompletion302Response();
                            }
                            // 可以添加最大重试次数限制
                        });
                    });
                }
            });
        }*/
    }
}
// 移动FITS文件到子目录
bool MainWindow:: moveFitsToSubfolder(const QString& sourceFile, const QString& savePath) {
    // 检查源文件是否存在
    if (!QFile::exists(sourceFile)) {
        LOG_CAMERA("Source file does not exist:");
        return false;
    }

    // 创建目标目录（如果不存在）
    QDir saveDir(savePath);
    if (!saveDir.exists()) {
        if (!saveDir.mkpath(".")) {
            LOG_CAMERA("Failed to create directory:");
            return false;
        }
        LOG_CAMERA("Created directory:");
    }

    // 构造目标路径
    QFileInfo fileInfo(sourceFile);
    QString destination = savePath + "/" + fileInfo.fileName();

    // 避免文件名冲突
    if (QFile::exists(destination)) {
        QDateTime now = QDateTime::currentDateTime();
        QString timestamp = now.toString("yyyyMMdd_hhmmss");
        destination = savePath + "/" + fileInfo.baseName() + "_" + timestamp + "." + fileInfo.suffix();
    }

    // 执行移动操作
    if (QFile::rename(sourceFile, destination)) {
        LOG_CAMERA(QString("Moved%1 to %2" ).arg(sourceFile).arg(destination));
        return true;
    } else {
        // 如果移动失败，尝试复制后删除
        if (QFile::copy(sourceFile, destination)) {
            QFile::remove(sourceFile);
            LOG_CAMERA("Copied & removed original:");
            return true;
        } else {
            LOG_CAMERA("Failed to move/copy file:");
            return false;
        }
    }
}



// 新增辅助函数：发送完成响应
// void MainWindow::sendCompletion302Response()
// {
//     if(!m_pendingResponse.isValid()) return;

//     QString doneResponse = QString(
//                                "<ID=302><CamID=%1><TIME=%2><SHUTSTA=closed>"
//                                "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
//                                "<SENDTIME=%5><FITS_Name=%6>;")
//                                .arg(m_pendingResponse.camId)
//                                .arg(m_pendingResponse.expTime, 0, 'f', 2)
//                                .arg(m_pendingResponse.expStartTimeStr)
//                                .arg(hwStatus.currentTemp, 0, 'f', 2)
//                                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
//                                .arg(m_pendingResponse.imageName);

//     sendResponse(doneResponse, m_pendingResponse.client);
//     LOG_CAMERA("Final completion response sent");
//     if (m_pendingResponse.client) {
//         m_pendingResponse.client->disconnectFromHost();
//     }
//     // 清理资源
//     m_receivedTags.clear();
//     m_convertedTags.clear();

//     connect(m_pendingResponse.client, &QTcpSocket::disconnected,
//             m_pendingResponse.client, &QObject::deleteLater);
// }

void MainWindow::sendResponseThreadSafe(const QString &response, QTcpSocket *client)
{
    QPointer<QTcpSocket> sock = client; // 防悬挂
    if (!sock) return;

    auto doSend = [sock, response]() {
        if (!sock) return;
        if (sock->state() != QAbstractSocket::ConnectedState) return;
        sock->write(response.toUtf8());
        sock->flush(); // 让内核缓冲尽快发送（可选）
    };

    // 如果已经在 socket 所在线程，直接发；否则投递回去
    if (sock->thread() == QThread::currentThread()) {
        doSend();
    } else {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
        // Qt 5.10+ 支持 functor 版 invokeMethod（推荐）
        QMetaObject::invokeMethod(sock, doSend, Qt::QueuedConnection);
#else
        // 旧版本：用 QTimer::singleShot 作为“排队回到对象线程”的通用技巧
        QTimer::singleShot(0, sock, doSend);
#endif
    }
    LOG_CAMERA(response);
}
void MainWindow::sendCompletion302Response()
{
    if (!m_pendingResponse.isValid()) return;

    // 组装响应
    const QString doneResponse = QString(
        "<ID=302><CamID=%1><TIME=%2><SHUTSTA=closed>"
        "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
        "<SENDTIME=%5><FITS_Name=%6>;")
        .arg(m_pendingResponse.camId)
        .arg(m_pendingResponse.expTime, 0, 'f', 2)
        .arg(m_pendingResponse.expStartTimeStr)
        .arg(hwStatus.currentTemp, 0, 'f', 2)
        .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
        .arg(m_pendingResponse.imageName);

    QPointer<QTcpSocket> sock = m_pendingResponse.client;
    if (!sock) return;

    // 1) 线程安全地发送数据
    sendResponseThreadSafe(doneResponse, sock);

    LOG_CAMERA("Final completion response sent");

    // 2) 断开连接 & deleteLater 也必须在 socket 所在线程执行
    auto doDisconnectAndCleanup = [sock]() {
        if (!sock) return;
        // 只连接一次防止重复 connect
        QObject::connect(sock, &QTcpSocket::disconnected,
                         sock, &QObject::deleteLater,
                         Qt::UniqueConnection);
        // 触发断开
        sock->disconnectFromHost();
        // 如果服务器端不主动发 FIN，必要时可以加：
        // if (sock->state() != QAbstractSocket::UnconnectedState)
        //     sock->close();
    };

    if (sock->thread() == QThread::currentThread()) {
        doDisconnectAndCleanup();
    } else {
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
        QMetaObject::invokeMethod(sock, doDisconnectAndCleanup, Qt::QueuedConnection);
#else
        QTimer::singleShot(0, sock, doDisconnectAndCleanup);
#endif
    }

    // 3) 清理你自有的状态（普通成员，通常在主线程就地清即可）
    m_receivedTags.clear();
    m_convertedTags.clear();
}


// 0818新增：清理函数
void MainWindow::cleanupConversionProcesses()
{
    foreach (QProcess* process, m_bin2fitsProcesses) {
        process->terminate();
        if (!process->waitForFinished(1000)) {
            process->kill();
        }
        delete process;
    }
    m_bin2fitsProcesses.clear();
}

// 0816新增：查询相机状态
void MainWindow::queryCameraStatus(std::function<void()> callback)
{
    LOG_CAMERA("Querying camera status...");
    sendCameraHeartCommand("camera_status");

    // 连接状态响应信号
    static QMetaObject::Connection conn;
    conn = connect(CameraHeatSocket, &QUdpSocket::readyRead, this, [this, callback]() {
        QByteArray datagram;
        datagram.resize(CameraHeatSocket->pendingDatagramSize());
        CameraHeatSocket->readDatagram(datagram.data(), datagram.size());

        QString response(datagram);
        LOG_CAMERA(response);
        if (response.startsWith("camera_status:")) {
            // 解析状态
            QStringList parts = response.mid(response.indexOf(':')+1).split('$');
            foreach (const QString &part, parts) {
                QStringList kv = part.split('=');
                if (kv.size() == 2) {
                    if (kv[0] == "ready") {
                        cameraState.status = kv[1].toInt();
                    }
                }
            }
            disconnect(conn); // 断开临时连接
            if(callback) callback();
        }
    });
}

void MainWindow::processSequenceNextFrame(){

    LOG_GENERAL(QString("进入processSequenceNextFrame函数，当前连续拍摄序列为 [%1]，共%2个任务").arg(currentSequenceName).arg(sequenceTasks.size()));

    if (!isSequenceRunning || ++currentSequenceIndex >= sequenceTasks.size()) {
        // 序列完成
        LOG_GENERAL("连续拍摄序列已完成");
        sequenceFinished(); // 发射完成信号
        progressTimer->stop();
        //ui->labelProgressStatus->setText("序列完成");
        ui->btnStartExposure->setEnabled(true); // added by XYH @ 20250527
        return;
    }

    const SequenceTask &task = sequenceTasks[currentSequenceIndex];

    // 设置基础文件名
    currentBaseFilename = task.filename;
    ui->lineEditBaseFilename->setText(currentBaseFilename);

    // 更新UI显示当前进度
    /*
    ui->labelSequenceProgress->setText(
        QString("正在拍摄: %1/%2 - %3")
            .arg(currentSequenceIndex + 1)
            .arg(sequenceTasks.size())
            .arg(currentBaseFilename)
        );*/

    // 设置曝光参数
    // LOG_GENERAL(QString("@@@@@ expTime: %1 s").arg(task.expTime));

    ui->doubleSpinBoxExposureTime->setValue(task.expTime);
    //ui->checkBoxDarkFrame->setChecked(task.dark);
    ui->checkBoxPinning->setChecked(task.pinning);
    ui->checkBoxDump->setChecked(task.dump);

    // 开始曝光
    on_btnStartExposure_clicked();
    LOG_GENERAL(QString("开始序列任务 %1/%2").arg(currentSequenceIndex+1).arg(sequenceTasks.size()));
}

QString MainWindow::beijingToUTC(const QString& beijingTime) {
    // 解析北京时间
    QDateTime beijingDt = QDateTime::fromString(beijingTime, "yyyy-MM-ddTHH:mm:ss.zzz");

    // 设置为北京时间（UTC+8）
    beijingDt.setTimeZone(QTimeZone("Asia/Shanghai"));

    // 转换为UTC
    QDateTime utcDt = beijingDt.toUTC();

    return utcDt.toString("yyyy-MM-ddTHH:mm:ss.zzzZ");
}

void MainWindow::handleFrameDone4720(const QString &response)
{
    QStringList parts = response.split(':');
    QString tag;

    LOG_CAMERA("enter handleframedone4720");
    foreach (const QString &part, parts) {
        if (part.startsWith("done=")) {
            tag = part.mid(5);

            break;
        }
    }

    if (tag.isEmpty()) {
        LOG_CAMERA("Invalid task ID");
        return;
    }

    // 记录收到的tag
    m_receivedTags4720.insert(tag);

    // 如果该tag已经在转换中或已完成，则忽略
    if (m_bin2fitsProcesses4720.contains(tag) || m_convertedTags4720.contains(tag)) {
        LOG_CAMERA(QString("Tag %1 is already being processed").arg(tag));
        return;
    }

    // 准备转换参数
    QString binFile = QString("%1/%2.bin").arg(binPath).arg(tag);
    QString fitsFile = QString("%1/%2_%3.fits").arg(fitsSavePath4720)
                           .arg(currentBaseFilename4720)
                           .arg(tag);
    fitsFilePaths4720.append(fitsFile);

    QString python = "/home/csst/anaconda3/bin/python3";
    QString script = "/home/csst/Downloads/bin2fits_4720_debug.py";

    QStringList arguments;
    arguments << script
              << binFile
              << fitsSavePath4720
              << fitsFile;
    // << "--selected-output" << fitsFile1
    // << "--averaged-output" << fitsFile2;


    // 创建并启动非阻塞转换进程
    QProcess* process = new QProcess(this);
    process->setProperty("tag", tag); // 保存tag信息
    process->setProperty("fitsFile", fitsFile);
    LOG_CAMERA(binFile);
    LOG_CAMERA(fitsFile);

    connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
            this, &MainWindow::handleConversionFinished4720);
    connect(process, &QProcess::errorOccurred,
            this, &MainWindow::handleConversionError4720);

    //    QString python = "/home/csst/anaconda3/bin/python3";

    //QString script = "/usr/local/bin/bin2fits_250522.py";

    LOG_CAMERA(QString("Starting parallel conversion for tag %1").arg(tag));
    //process->start(python, {script, binFile, fitsSavePath4720, currentBaseFilename4720});
    process->start(python, arguments);

    m_bin2fitsProcesses4720[tag] = process;
}

void MainWindow::handleConversionFinished4720(int exitCode, QProcess::ExitStatus exitStatus)
{
    QProcess* process = qobject_cast<QProcess*>(sender());
    if (!process) return;

    QString tag = process->property("tag").toString();
    QString fitsFile = process->property("fitsFile").toString();
    //LOG_CAMERA(fitsFile);

    if (exitStatus == QProcess::NormalExit && exitCode == 0) {
        LOG_CAMERA(QString("Conversion succeeded for tag %1").arg(tag));
        {
            QMutexLocker locker(&m_tagsMutex);  // 自动加锁
            m_convertedTags4720.insert(tag);        // 受保护的操作
        }

        // 更新FITS头信息
        LOG_CAMERA(fitsFile);
        updateFitsHeaderAsync4720(fitsFile);

        // 记录历史
        //recordShootingHistory(QFileInfo(fitsFile).fileName(), fitsSavePath);
    } else {
        LOG_CAMERA(QString("Conversion failed for tag %1, code: %2")
                       .arg(tag).arg(exitCode));
    }

    // 清理进程
    m_bin2fitsProcesses4720.remove(tag);
    process->deleteLater();

    // 检查是否全部完成
    checkAllConversionsDone4720();
}

void MainWindow::updateFitsHeaderAsync4720(const QString& fitsFile)
{
    LOG_CAMERA("enter updateFitsHeaderAsync 4720");
    std::vector<std::pair<std::string, std::string>> keywords;

    if (m_currentExpInfo4720.isValid()) {
        LOG_CAMERA("m_currentExpInfo4720.isValid");
        double expTime = m_currentExpInfo4720.expStart.msecsTo(m_currentExpInfo4720.expEnd) / 1000.0;

        keywords.emplace_back("EXPSTART", m_currentExpInfo4720.expStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPEND", m_currentExpInfo4720.expEnd.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("READOUTS", m_currentExpInfo4720.readoutStart.toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
        keywords.emplace_back("EXPTIME", QString::number(expTime, 'f', 3).toStdString());
        keywords.emplace_back("DATA_OBS",QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz").toStdString());
    }

    QtConcurrent::run([this, fitsFile, keywords]() {
        QJsonObject headerObj;
        for (const auto& keyword : keywords) {
            headerObj.insert(QString::fromStdString(keyword.first),
                             QString::fromStdString(keyword.second));
        }
        updateFitsHeader(fitsFile, headerObj);
    });
}

// 处理转换错误
void MainWindow::handleConversionError4720(QProcess::ProcessError error)
{
    QProcess* process = qobject_cast<QProcess*>(sender());
    if (!process) return;

    QString tag = process->property("tag").toString();
    LOG_CAMERA(QString("Conversion error for tag %1: %2")
                   .arg(tag).arg(process->errorString()));

    m_bin2fitsProcesses4720.remove(tag);
    process->deleteLater();
}

// 检查所有转换是否完成
void MainWindow::checkAllConversionsDone4720()
{
    if (m_receivedTags4720.size() == 1 && m_convertedTags4720.size() == 1) {
        LOG_CAMERA("All 1 BIN files converted successfully");
        QString mergedFitsPath = fitsSavePath4720 + "/" + currentBaseFilename4720 +".fits";

        QString mergefitsfile = currentBaseFilename4720 +".fits";
        //if (mergeFitsFiles(fitsFilePaths, mergedFitsPath, )) {
        QString imagetype = ui->comBoxframetype->currentData().toString();
        QString date_ = QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ss.zzz");
        QString readmode = m_currentExpInfo.readoutRate;
        QString channel = "yellow";
        QString imgrect = "";
        QString CCD_SN = "";
        double expreq = ui->doubleSpinBoxExposureTime->value();

        if (mergeFitsFiles4720(fitsFilePaths4720, mergedFitsPath, mergefitsfile, fitsSavePath4720, date_,
                               CCD_SN,imagetype, imgrect, channel, expreq)) {
            LOG_CAMERA(QString("mergeFitsFiles successfully: %1").arg(mergedFitsPath));

            recordShootingHistory(mergedFitsPath, fitsSavePath4720); // 记录历史
            QString subFolderPath = fitsSavePath4720 + "/sub";
            QDir().mkpath(subFolderPath);  // 创建目录
            //for (const QString &fitsFile : fitsFilePaths4720) {
               // if (!moveFitsToSubfolder(fitsFile, subFolderPath)) {
                    //qCritical() << "Failed to process file:" << fitsFile;
                //}
            //}
            fitsFilePaths4720.clear();
            QMetaObject::invokeMethod(this, [this, mergedFitsPath]() {
                if (ui->checkboxShowFits_2->isChecked()) {
                    launchDS9(mergedFitsPath);
                }
            }, Qt::QueuedConnection);
        } else {
            LOG_CAMERA("mergeFitsFiles failed");
            fitsFilePaths4720.clear();
        }

        if( m_pendingResponse4720.isValid() ){
            sendCompletion302Response4720();
        }
        /*if (!m_currentExpInfo.isManual && m_pendingResponse.isValid()) {
            queryCameraStatus([this]() {
                if (cameraState.status == 0) {
                    sendCompletion302Response();
                } else {
                    QTimer::singleShot(500, this, [this]() {
                        queryCameraStatus([this]() { // 保持递归调用
                            if (cameraState.status == 0) {
                                sendCompletion302Response();
                            }
                            // 可以添加最大重试次数限制
                        });
                    });
                }
            });
        }*/
    }
}

void MainWindow::sendCompletion302Response4720()
{
    LOG_CAMERA("FUCK!");
    if (!m_pendingResponse4720.isValid()) return;

    // 组装响应
    const QString doneResponse = QString(
                                     "<ID=602><CamID=%1><TIME=%2><SHUTSTA=closed>"
                                     "<EXPST_TIME=%3><CUR_TEM=%4><INFO=OK><ACTION=DONE>"
                                     "<SENDTIME=%5><FITS_Name=%6>;")
                                     .arg(m_pendingResponse4720.camId)
                                     .arg(m_pendingResponse4720.expTime, 0, 'f', 2)
                                     .arg(m_pendingResponse4720.expStartTimeStr)
                                     .arg(hwStatus.currentTemp, 0, 'f', 2)
                                     .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))
                                     .arg(m_pendingResponse4720.imageName);

    QPointer<QTcpSocket> sock = m_pendingResponse4720.client;
    if (!sock) return;

    // 1) 线程安全地发送数据
    sendResponseThreadSafe(doneResponse, sock);

    LOG_CAMERA("Final completion response sent");

    // 2) 断开连接 & deleteLater 也必须在 socket 所在线程执行
    auto doDisconnectAndCleanup = [sock]() {
        if (!sock) return;
        // 只连接一次防止重复 connect
        QObject::connect(sock, &QTcpSocket::disconnected,
                         sock, &QObject::deleteLater,
                         Qt::UniqueConnection);
        // 触发断开
        sock->disconnectFromHost();
        // 如果服务器端不主动发 FIN，必要时可以加：
        // if (sock->state() != QAbstractSocket::UnconnectedState)
        //     sock->close();
    };

    if (sock->thread() == QThread::currentThread()) {
        doDisconnectAndCleanup();
    } else {
#if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
        QMetaObject::invokeMethod(sock, doDisconnectAndCleanup, Qt::QueuedConnection);
#else
        QTimer::singleShot(0, sock, doDisconnectAndCleanup);
#endif
    }

    // 3) 清理你自有的状态（普通成员，通常在主线程就地清即可）
    m_receivedTags4720.clear();
    m_convertedTags4720.clear();
}

